/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
    PURPOSE.

Module Name:

    AMCC5933.c - Driver for the AMCC S5933 PCI chipset reference kit.

Abstract:

Environment:

    Kernel mode

--*/

#include "AMCC5933.h"
#include "AMCC5933.tmh" // auto-generated by WPP

//
// Make sure the initialization code is removed from memory after use.
//

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, AmccPciAddDevice)
#pragma alloc_text(PAGE, AmccPciEvtDevicePrepareHardware)
#pragma alloc_text(PAGE, AmccPciEvtDeviceReleaseHardware)
#pragma alloc_text(PAGE, AmccPciContextCleanup)
#pragma alloc_text(PAGE, AmccPciEvtDeviceD0Exit)
#endif


NTSTATUS
AmccPciAddDevice(
    __in    WDFDRIVER        Driver,
    __inout PWDFDEVICE_INIT  DeviceInit
    )
/*++

Routine Description:

    EvtDeviceAdd is called by the framework in response to AddDevice
    call from the PnP manager.  It is responsible for initializing and
    creating a WDFDEVICE object.

    Any work that should be done after the object is created should be
    deferred until EvtDeviceSoftwareInit, as that callback will be made
    with the device lock held (if there is one.)

Arguments:

    Driver - Handle to a framework driver object created in DriverEntry

    DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.

Return Value:

    NTSTATUS

--*/
{
    NTSTATUS                   status = STATUS_SUCCESS;
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_OBJECT_ATTRIBUTES       fdoAttributes;
    WDF_INTERRUPT_CONFIG        interruptConfig;
    WDF_OBJECT_ATTRIBUTES       interruptAttributes;
    WDF_IO_QUEUE_CONFIG         ioQueueConfig;
    PAMCC_DEVICE_EXTENSION           devExt;
    WDFQUEUE                    hQueue;
    WDFDEVICE                   device;

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                        "AmccPciAddDevice: 0x%p", Driver);

    //
    // Zero out the PnpPowerCallbacks structure.
    //
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);

    //
    // Set Callbacks for any of the functions we are interested in.
    // If no callback is set, Framework will take the default action
    // by itself.
    //
    pnpPowerCallbacks.EvtDevicePrepareHardware = AmccPciEvtDevicePrepareHardware;
    pnpPowerCallbacks.EvtDeviceReleaseHardware = AmccPciEvtDeviceReleaseHardware;
    pnpPowerCallbacks.EvtDeviceD0Entry         = AmccPciEvtDeviceD0Entry;
    pnpPowerCallbacks.EvtDeviceD0Exit          = AmccPciEvtDeviceD0Exit;

    //
    // Register the PnP Callbacks..
    //
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    //
    // Set various attributes for this device
    //
    WdfDeviceInitSetIoType( DeviceInit, WdfDeviceIoDirect );

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fdoAttributes, AMCC_DEVICE_EXTENSION);
    fdoAttributes.EvtCleanupCallback = AmccPciContextCleanup;
    //
    // We want all the queue callbacks, cancel routine, and DpcForIsr to be serialized
    // at the device level, so we don't have worry about any synchronization issues.
    //
    fdoAttributes.SynchronizationScope = WdfSynchronizationScopeDevice;

    status = WdfDeviceCreate( &DeviceInit, &fdoAttributes, &device );

    if ( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                                "WdfDeviceInitialize failed 0x%X", status);
        return status;
    }

    //
    // Device Initialization is complete.
    // Get the Device Extension and initialize it.
    //
    devExt = AmccPciGetDevExt(device);

    devExt->Device = device;

    TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                        "PDO 0x%p, FDO 0x%p, DevExt 0x%p",
                        WdfDeviceWdmGetPhysicalDevice(device),
                        WdfDeviceWdmGetDeviceObject( device ), devExt);

    //
    // This device generates an interrupt.  So create an interrupt object which
    // will later be associated with the devices resources and connected
    // by the Framework.
    //

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&interruptAttributes, INTERRUPT_DATA);

    //
    // Configure the Interrupt object
    //
    WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,
                              AmccPciEvtInterruptIsr,
                              AmccPciEvtInterruptDpc);

    status = WdfInterruptCreate(device,
                                &interruptConfig,
                                &interruptAttributes,
                                &devExt->WdfInterrupt);
    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                    "WdfInterruptCreate failed: %!STATUS!\n", status);
        return status;
    }

    //
    // The S5933 requires DMA buffers be aligned on a 32-bit boundary
    //
    // NOTE: Read the existing alignment value. If it is greater than
    //       or equal then keep it.  If it is less then update the
    //       alignment requirement field with this device's required
    //       value.
    //
    // NOTE: See the MSDN section titled "Initializing a Device Object"
    //       for details on how to specify this alignment value.
    //
    // NOTE: AMCC5933_ALIGNMENT__32BITS is equated to (4-1) for 32-bit
    //       alignment.
    //
    {
        ULONG alignReq;

        alignReq = WdfDeviceGetAlignmentRequirement( device );

        if (alignReq < AMCC5933_ALIGNMENT__32BITS) {

            //
            // Set the S5933 alignment requirement as new value.
            //
            WdfDeviceSetAlignmentRequirement( device,
                                              AMCC5933_ALIGNMENT__32BITS);
        }
    }

    //
    // Register I/O callbacks.
    //
    // Create a sequential IO Queue for serial operation. That means all the requests (Read/Write
    // & IOCTL) are serialized to the device. Until the driver completes the request presented to it,
    // the framework will not schedule another one. The requests held in the framework will be
    // cancelled automatically if the source of request (application) terminate or cancels it.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( &ioQueueConfig,
                              WdfIoQueueDispatchSequential);

    ioQueueConfig.EvtIoDefault = AmccPciEvtIoDefault;

    status = WdfIoQueueCreate( device,
                                          &ioQueueConfig,
                                          WDF_NO_OBJECT_ATTRIBUTES,
                                          &hQueue );
    if (!NT_SUCCESS (status)) {
        //
        // We don't have worry about deleting the device here because framework will automatically
        // cleanup that when the driver unloads.
        //
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                                "WdfIoQueueCreate failed %!STATUS!", status);
        return status;
    }

    //
    // Register an interface so that application can find and talk to us.
    // NOTE: See the note in Public.h concerning this GUID value.
    //
    status = WdfDeviceCreateDeviceInterface( device,
                                             (LPGUID) &GUID_DEVINTERFACE_AMCC_PCI,
                                             NULL );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                "<-- AMCCAddDevice: WdfDeviceCreateDeviceInterface failed %!STATUS!", status);
        return status;
    }

    devExt->MaximumTransferLength = MAXIMUM_REQUEST_CONTEXT_LENGTH;

    //
    // Set the maximum physical pages for now, but this value may change if
    // there aren't enough map registers
    //
    devExt->MaximumPhysicalPages = MAXIMUM_PHYSICAL_PAGES;

    return status;
}

NTSTATUS
AmccPciEvtDevicePrepareHardware(
    __in WDFDEVICE       Device,
    __in WDFCMRESLIST   Resources,
    __in WDFCMRESLIST   ResourcesTranslated
    )
/*++

Routine Description:

    EvtDevicePrepareHardware event callback performs operations that are necessary
    to use the device's control registers.

Arguments:

    Device - Handle to a framework device object.

    Resources - Handle to a collection of framework resource objects.
                This collection identifies the raw (bus-relative) hardware
                resources that have been assigned to the device.

    ResourcesTranslated - Handle to a collection of framework resource objects.
                This collection identifies the translated (system-physical)
                hardware resources that have been assigned to the device.
                The resources appear from the CPU's point of view.
                Use this list of resources to map I/O space and
                device-accessible memory into virtual address space

Return Value:

    WDF status code.

    Let us not worry about cleaning up the resources here if we fail start,
    because the PNP manager will send a remove-request and we will free all
    the allocated resources in AmccPciEvtDeviceReleaseHardware.

--*/
{
    ULONG                               i;
    NTSTATUS                            status = STATUS_SUCCESS;
    PAMCC_DEVICE_EXTENSION                   devExt = NULL;
    BOOLEAN                             foundPort      = FALSE;
    PHYSICAL_ADDRESS                    portBasePA     = {0};
    ULONG                               portCount      = 0;
    WDF_DMA_ENABLER_CONFIG              dmaConfig;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR     desc;

    PAGED_CODE();

    //
    // The Resources collection is not used for PCI devices, since the PCI
    // bus driver manages the device's PCI Base Address Registers.
    //

    UNREFERENCED_PARAMETER( Resources );


    devExt = AmccPciGetDevExt(Device);

    //
    // Parse the resource list and save the resource information.
    //
    for (i=0; i < WdfCmResourceListGetCount(ResourcesTranslated); i++) {

        desc = WdfCmResourceListGetDescriptor(ResourcesTranslated, i);

        if(!desc) {
            TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                            "WdfResourceCmGetDescriptor failed");
            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }

        switch (desc->Type) {

        case CmResourceTypePort:

            portBasePA = desc->u.Port.Start;
            portCount  = desc->u.Port.Length;

            devExt->PortMapped =
                (desc->Flags & CM_RESOURCE_PORT_IO) ? FALSE : TRUE;

            foundPort = TRUE;

            //
            // Map in the single IO Space resource.
            //
            if (devExt->PortMapped) {

                devExt->PortBase =
                    (PUCHAR) MmMapIoSpace( portBasePA, portCount, MmNonCached );

                if (!devExt->PortBase) {

                    TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                                    "Unable to map port range 0x%p, length %d",
                                        devExt->PortBase, devExt->PortCount);

                    return STATUS_INSUFFICIENT_RESOURCES;
                }

                devExt->PortCount = portCount;

            } else {

                devExt->PortBase  = (PUCHAR)(ULONG_PTR) portBasePA.QuadPart;
                devExt->PortCount = portCount;
            }

            devExt->Regs = (PREG5933) devExt->PortBase;

            break;

        default:
            TraceEvents(TRACE_LEVEL_WARNING, AMCC_TRACE_INIT,
                                "Unknown resource type %d", desc->Type);
            break;
        }

    }

    if (!foundPort) {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                            "Missing hardware resources");
        return STATUS_DEVICE_CONFIGURATION_ERROR;
    }


    //
    // Configure the DMA  object
    //

    WDF_DMA_ENABLER_CONFIG_INIT( &dmaConfig,
                                 WdfDmaProfilePacket,
                                 devExt->MaximumTransferLength );

    status = WdfDmaEnablerCreate( devExt->Device,
                                  &dmaConfig,
                                  WDF_NO_OBJECT_ATTRIBUTES,
                                  &devExt->DmaEnabler );

    if (!NT_SUCCESS (status)) {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                       "WdfDmaEnablerCreate failed %08X", status);
        return status;
    }

    return STATUS_SUCCESS;
}

NTSTATUS
AmccPciEvtDeviceReleaseHardware (
    __in WDFDEVICE       hDevice,
    __in WDFCMRESLIST   ResourcesTranslated
    )
/*++

Routine Description:

    EvtDeviceReleaseHardware is called by the framework whenever the PnP manager
    is revoking ownership of our resources.  This may be in response to either
    IRP_MN_STOP_DEVICE or IRP_MN_REMOVE_DEVICE.  The callback is made before
    passing down the IRP to the lower driver.

    In this callback, do anything necessary to free those resources.

Arguments:

    Device - Handle to a framework device object.

    ResourcesTranslated - Handle to a collection of framework resource objects.
                This collection identifies the translated (system-physical)
                hardware resources that have been assigned to the device.
                The resources appear from the CPU's point of view.
                Use this list of resources to map I/O space and
                device-accessible memory into virtual address space

Return Value:

    VOID
--*/
{
    PAMCC_DEVICE_EXTENSION   devExt = NULL;

    UNREFERENCED_PARAMETER(ResourcesTranslated);

    PAGED_CODE();

    devExt = AmccPciGetDevExt(hDevice);

    //
    // Unmap the IO resource
    //
    if (devExt->PortBase) {

        if (devExt->PortMapped) {

            MmUnmapIoSpace( devExt->PortBase,  devExt->PortCount );

        }
        devExt->PortBase = NULL;
    }

    return STATUS_SUCCESS;
}


VOID
AmccPciContextCleanup(
    __in WDFOBJECT   Device
    )
/*++

Routine Description:

   EvtDeviceContextCleanup event callback must perform any operations that are
   necessary before the specified device is removed. The framework calls
   the driver's EvtDeviceRemove callback when the PnP manager sends
   an IRP_MN_REMOVE_DEVICE request to the driver stack. Function driver
   typically undo whatever they did in EvtDeviceSoftwareInit callback.

Note:

    It's not necessary to delete the WDFINTERRUPT object created in
    EvtDeviceSoftwareInit, since the Framework will automatically clean that
    up.

Arguments:

    Device - Handle to a framework device object.

Return Value:

    VOID

--*/
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER(Device);
}



BOOLEAN
AmccPciInitializeHardware(
    IN PVOID Context
    )
/*++

Routine Description:

Arguments:

    Context - Driver determined context information.
              For this driver, the context is a pointer to the device
              extension.

Return Value:

    TRUE

--*/
{
    PAMCC_DEVICE_EXTENSION   devExt = Context;

    UNREFERENCED_PARAMETER( devExt );

    return TRUE;
}

NTSTATUS
AmccPciEvtDeviceD0Entry(
    __in IN  WDFDEVICE Device,
    __in IN  WDF_POWER_DEVICE_STATE PreviousState
    )
/*++

Routine Description:

    EvtDeviceD0Entry event callback must perform any operations that are
    necessary before the specified device is used.  It will be called every
    time the hardware needs to be (re-)initialized.  This includes after
    IRP_MN_START_DEVICE, IRP_MN_CANCEL_STOP_DEVICE, IRP_MN_CANCEL_REMOVE_DEVICE,
    IRP_MN_SET_POWER-D0.

    This function is not marked pageable because this function is in the
    device power up path. When a function is marked pagable and the code
    section is paged out, it will generate a page fault which could impact
    the fast resume behavior because the client driver will have to wait
    until the system drivers can service this page fault.

Arguments:

    Device - Handle to a framework device object.

    PreviousState - Device power state which the device was in most recently.
        If the device is being newly started, this will be
        PowerDeviceUnspecified.

Return Value:

    NTSTATUS

--*/
{
    UNREFERENCED_PARAMETER(Device);
    UNREFERENCED_PARAMETER(PreviousState);

    return STATUS_SUCCESS;
}


NTSTATUS
AmccPciEvtDeviceD0Exit(
    __in IN  WDFDEVICE Device,
    __in IN  WDF_POWER_DEVICE_STATE TargetState
    )
/*++

Routine Description:

   EvtDeviceD0Exit event callback must perform any operations that are
   necessary before the specified device is moved out of the D0 state.  If the
   driver needs to save hardware state before the device is powered down, then
   that should be done here.

Arguments:

    Device - Handle to a framework device object.

    TargetState - Device power state which the device will be put in once this
        callback is complete.

Return Value:

    NTSTATUS

--*/
{
    UNREFERENCED_PARAMETER(Device);
    UNREFERENCED_PARAMETER(TargetState);

    PAGED_CODE();

    return STATUS_SUCCESS;
}


